package at.fhooe.mc.control;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
import at.fhooe.mc.control.automation.IAutoAlgorithm;
import at.fhooe.mc.control.automation.IAutomationControl;
import at.fhooe.mc.control.automation.SingleElevatorControlAlgorithm;
import at.fhooe.mc.control.exception.IndexChangeException;
import at.fhooe.mc.control.exception.SameClockTickException;
import at.fhooe.mc.control.manual.IManualControl;
import at.fhooe.mc.data.ControlMode;
import at.fhooe.mc.data.DirectionType;
import at.fhooe.mc.data.ElevatorData;
import at.fhooe.mc.data.FloorData;
import at.fhooe.mc.data.SystemData;
import at.fhooe.mc.data.control.ElevatorControlData;
import at.fhooe.mc.model.ElevatorControlCenterObservable;
import at.fhooe.mc.model.ElevatorControlCenterObserver;
import at.fhooe.mc.view.ElevatorControlCenterView;

/**
 * main module, that handles the elevators
 *
 * @author Thomas Wirth
 *
 */
public class ElevatorControlCenter implements Runnable,
		ElevatorControlCenterObservable, IManualControl, IAutomationControl
{
	private static final long CLOCK_ERROR_DELTA_TIME = 5000;

	private IAutoAlgorithm autoAlgorithm;
	private IAdapter adapter;
	private SystemData data = null;
	private List<ElevatorControlData> controlElevatorData;

	private ArrayList<ElevatorControlCenterObserver> observers;

	private long lastTimeInvalidClock = -1;

	public ElevatorControlCenter()
	{
		observers = new ArrayList<ElevatorControlCenterObserver>();
		controlElevatorData = new ArrayList<ElevatorControlData>();
	}

	// ===========================================================================
	/**
	 * @param adapter the adapter to set
	 */
	public void setAdapter(IAdapter adapter)
	{
		this.adapter = adapter;
	}

	// ===========================================================================
	/**
	 * @param autoAlgorithm the autoAlgorithm to set
	 */
	public void setAutoAlgorithm(IAutoAlgorithm autoAlgorithm)
	{
		this.autoAlgorithm = autoAlgorithm;
	}

	// ===========================================================================
	/**
	 * @return the controlElevatorData
	 */
	public List<ElevatorControlData> getControlElevatorData()
	{
		return controlElevatorData;
	}

	// ===========================================================================
	public static void main(String[] args)
	{
		ElevatorControlCenter ecc = new ElevatorControlCenter();
		ElevatorControlCenterView eccView = new ElevatorControlCenterView(ecc);
		ecc.registerObserver(eccView);

		ecc.setAdapter(new RemoteElevatorAdapter());
		ecc.setAutoAlgorithm(new SingleElevatorControlAlgorithm());
		ecc.start();
	}

	// ===========================================================================
	public boolean start()
	{
		if (init())
		{
			Thread thread = new Thread(this);
			thread.start();
			return true;
		}
		return false;
	}

	// ===========================================================================
	public boolean init()
	{
		return adapter.init();
	}

	// ===========================================================================
	public synchronized void doWork() throws Exception
	{
		adapter.updateNewTargets(data, controlElevatorData);
		for (int ii = 0; ii < controlElevatorData.size(); ii++)
		{
			controlElevatorData.get(ii).setNewTarget(
				ElevatorControlData.NO_TARGET);
		}

		SystemData newData = adapter.parseData(data, controlElevatorData);
		if (checkIfThereAreChanges(newData))
		{
			data = newData;
			notifyObservers();

		}
		autoAlgorithm.newValuesAvailable(data, this);
	}

	// ===========================================================================
	@Override
	public void run()
	{
		try
		{
			data = adapter.parseData(null, null);

			autoAlgorithm.init(data);

			for (int ii = 0; ii < data.getElevators().size(); ii++)
			{
				controlElevatorData.add(new ElevatorControlData());
			}

			notifyObservers();

			autoAlgorithm.newValuesAvailable(data, this);
		}
		catch (Exception e)
		{
			JOptionPane.showMessageDialog(null,
				"Unable to initialize the application", "Error",
				JOptionPane.ERROR_MESSAGE);
			return;
		}

		try
		{
			while (true)
			{
				try
				{
					doWork();
					Thread.sleep(100);
				}
				catch (SameClockTickException scte)
				{
					if (System.currentTimeMillis() - lastTimeInvalidClock > CLOCK_ERROR_DELTA_TIME)
					{
						JOptionPane.showMessageDialog(
							null,
							"Remote Divice is not working correctly. No valid Clock Tick Update received.",
							"Error", JOptionPane.ERROR_MESSAGE);
						lastTimeInvalidClock = System.currentTimeMillis();
					}
				}

			}
		}
		catch (RemoteException re)
		{
			JOptionPane.showMessageDialog(null,
				"Connection Problem with the Remote Elevator Device", "Error",
				JOptionPane.ERROR_MESSAGE);
		}
		catch (Exception e)
		{
			JOptionPane.showMessageDialog(
				null,
				"An fatal error occured. Check the settings of the application and the remote device",
				"Error", JOptionPane.ERROR_MESSAGE);
		}
	}

	// ===========================================================================
	private boolean checkIfThereAreChanges(FloorData data1, FloorData data2)
		throws IndexChangeException
	{
		if (data1.getFloorIndex() != data2.getFloorIndex())
		{
			throw new IndexChangeException();
		}

		if (data1.isFloorButtonDown() != data2.isFloorButtonDown())
		{
			return true;
		}

		if (data1.isFloorButtonUp() != data2.isFloorButtonUp())
		{
			return true;
		}

		return false;
	}

	// ===========================================================================
	private boolean checkIfThereAreChanges(ElevatorData data1,
		ElevatorData data2) throws IndexChangeException
	{
		if (data1.getElevatorIndex() != data2.getElevatorIndex())
		{
			throw new IndexChangeException();
		}

		if (data1.getMode() != data2.getMode())
		{
			return true;
		}

		if (data1.getElevatorFloor() != data2.getElevatorFloor())
		{
			return true;
		}

		if (data1.getElevatorPosition() != data2.getElevatorPosition())
		{
			return true;
		}

		if (data1.getCurrentTarget() != data2.getCurrentTarget())
		{
			return true;
		}

		if (data1.getCommittedDirection() != data2.getCommittedDirection())
		{
			return true;
		}

		if (data1.getElevatorDoorStatus() != data2.getElevatorDoorStatus())
		{
			return true;
		}

		if (data1.getElevatorSpeed() != data2.getElevatorSpeed())
		{
			return true;
		}

		if (data1.getElevatorAccel() != data2.getElevatorAccel())
		{
			return true;
		}

		if (data1.getElevatorCapacity() != data2.getElevatorCapacity())
		{
			return true;
		}

		if (data1.getElevatorWeight() != data2.getElevatorWeight())
		{
			return true;
		}

		for (int floorIndex = 0; floorIndex < data1.getElevatorButton().length; floorIndex++)
		{
			if (data1.getElevatorButton()[floorIndex] != data2.getElevatorButton()[floorIndex])
			{
				return true;
			}

			if (data1.getServicesFloors()[floorIndex] != data2.getServicesFloors()[floorIndex])
			{
				return true;
			}
		}

		return false;
	}

	// ===========================================================================
	private boolean checkIfThereAreChanges(SystemData newData)
		throws IndexChangeException
	{
		if (newData == null)
		{
			return false;
		}
		if (data == null)
		{
			return true;
		}

		if (data.getClockTick() == newData.getClockTick())
		{
			return false;
		}

		if (data.getFloorHeight() != newData.getFloorHeight())
		{
			return true;
		}

		for (int floorIndex = 0; floorIndex < data.getFloors().size(); floorIndex++)
		{
			if (checkIfThereAreChanges(data.getFloors().get(floorIndex),
				newData.getFloors().get(floorIndex)))
			{
				return true;
			}
		}

		for (int elevatorIndex = 0; elevatorIndex < data.getElevators().size(); elevatorIndex++)
		{
			if (checkIfThereAreChanges(data.getElevators().get(elevatorIndex),
				newData.getElevators().get(elevatorIndex)))
			{
				return true;
			}
		}

		return false;
	}

	// ===========================================================================
	@Override
	public void registerObserver(ElevatorControlCenterObserver observer)
	{
		observers.add(observer);
	}

	// ===========================================================================
	@Override
	public void unregisterObserver(ElevatorControlCenterObserver observer)
	{
		observers.remove(observer);
	}

	// ===========================================================================
	@Override
	public void notifyObservers()
	{
		for (int ii = 0; ii < observers.size(); ii++)
		{
			observers.get(ii).updateNewValues(data);
		}
	}

	// ===========================================================================
	@Override
	public synchronized void changeControlMode(int elevatorIndex,
		ControlMode mode)
	{
		controlElevatorData.get(elevatorIndex).setMode(mode);
	}

	// ===========================================================================
	@Override
	public synchronized void manualControl(int elevatorIndex, int newTarget)
	{
		if (controlElevatorData.get(elevatorIndex).getMode() == ControlMode.MANUAL)
		{
			controlElevatorData.get(elevatorIndex).setNewTarget(newTarget);
		}
	}

	@Override
	public synchronized void manualDirection(int elevatorIndex,
		DirectionType type)
	{
		if (controlElevatorData.get(elevatorIndex).getMode() == ControlMode.MANUAL)
		{
			controlElevatorData.get(elevatorIndex).setNewDirection(type);
		}
	}

	// ===========================================================================
	@Override
	public synchronized void autoControl(int elevatorIndex, int newTarget)
	{
		if (controlElevatorData.get(elevatorIndex).getMode() == ControlMode.AUTOMATIC)
		{
			controlElevatorData.get(elevatorIndex).setNewTarget(newTarget);
		}
	}
}
